﻿/*
Copyright (c) 2008 William Duff

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all 
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.ComponentModel;

namespace LiveUpload.Facebook
{
    /// <summary>
    /// Data related to this plugin that persists across sessions.
    /// </summary>
    public class PersistentData
    {
        #region Constructors

        /// <summary>
        /// Constructs a new PersistentData object to encapsulate persistXml.
        /// </summary>
        /// <param name="persistXml">
        /// The persistXml file passed by the IPublishPlugin interface.
        /// </param>
        public PersistentData(XmlDocument persistXml)
        {
            if (persistXml == null)
            {
                throw new ArgumentNullException("persistXml");
            }
            this.persistXml = persistXml;

            if (!IsInitialized())
            {
                Initialize();
            }

            this.preferencesNode = GetXmlElement("Preferences", this.persistXml);
            this.users = UsersExists() ? GetUsers() : new List<Account>();
            this.accountLinks = AccountLinksExists() ? GetAccountLinks() : new List<AccountLink>();
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// The persistXml file passed by the IPublishPlugin interface.
        /// </summary>
        private XmlDocument persistXml;

        /// <summary>
        /// Points to the "Preferences" XmlNode inside persistXml.
        /// </summary>
        private XmlNode preferencesNode;

        private List<Account> users;
        private List<AccountLink> accountLinks;

        #endregion

        #region Public Fields

        /// <summary>
        /// List of saved accounts.
        /// </summary>
        public List<Account> Users
        {
            get { return this.users; }
            set { this.users = value; }
        }

        /// <summary>
        /// The list of links between WLPG people tags and Facebook accounts.
        /// </summary>
        public List<AccountLink> AccountLinks
        {
            get { return this.accountLinks; }
            set { this.accountLinks = value; }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Gets an XmlElement, assuming it exists.
        /// </summary>
        /// <param name="elementName">
        /// The name of the element.
        /// </param>
        /// <param name="parentNode">
        /// The immediate parent of the element.
        /// </param>
        /// <returns>
        /// The specified XmlElement.
        /// </returns>
        private XmlElement GetXmlElement(string elementName, XmlNode parentNode)
        {
            if (elementName == null)
            {
                throw new ArgumentNullException("element");
            }
            if (parentNode == null)
            {
                throw new ArgumentNullException("parentNode");
            }

            XmlElement theElement = parentNode.SelectSingleNode(elementName) as XmlElement;
            if (theElement == null)
            {
                throw new ArgumentException(elementName + " does not exist.", "elementName");
            }
            return theElement;
        }

        /// <summary>
        /// Sets an XmlElement, replacing an existing XmlElement if necessary.
        /// </summary>
        /// <param name="element">
        /// The element to set.
        /// </param>
        /// <param name="parentNode">
        /// The immediate parent of the element.
        /// </param>
        private void SetXmlElement(XmlElement element, XmlNode parentNode)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }
            if (parentNode == null)
            {
                throw new ArgumentNullException("parentNode");
            }

            XmlElement existingElement =
                parentNode.SelectSingleNode(element.LocalName) as XmlElement;
            if (existingElement == null)
            {
                parentNode.AppendChild(element);
            }
            else
            {
                parentNode.ReplaceChild(element, existingElement);
            }
        }

        /// <summary>
        /// Tries to grab a list of saved users using older schemas. Be careful, 
        /// some of the member variables may be uninitialized.
        /// </summary>
        /// <returns>
        /// A list of saved users.
        /// </returns>
        private List<XmlElement> GetSavedUsers()
        {
            List<XmlElement> savedUsers = new List<XmlElement>();

            // The old version 1.0.0.* schema.
            XmlNodeList userNodes = this.persistXml.SelectNodes("users/user");
            foreach(XmlNode userNode in userNodes)
            {
                XmlElement userElement = userNode as XmlElement;
                if(userElement != null) 
                {
                    savedUsers.Add(userElement);
                }
            }

            // The newer 2.0.0.* schema.
            userNodes = this.persistXml.SelectNodes("Preferences/Users/User");
            foreach (XmlNode userNode in userNodes)
            {
                XmlElement userElement = userNode as XmlElement;
                if (userElement != null)
                {
                    savedUsers.Add(userElement);
                }
            }

            return savedUsers;
        }

        /// <summary>
        /// Checks to see if the users element is present.
        /// </summary>
        /// <returns>
        /// true if the element is present and false otherwise.
        /// </returns>
        private bool UsersExists()
        {
            XmlNode usersNode = this.preferencesNode.SelectSingleNode("Users");
            return usersNode != null;
        }

        /// <summary>
        /// Gets a list of all user accounts.
        /// </summary>
        /// <returns>
        /// A list of user accounts.
        /// </returns>
        private List<Account> GetUsers()
        {
            XmlElement usersElement = GetXmlElement("Users", this.preferencesNode);

            List<Account> users = new List<Account>();

            XmlNodeList userNodes = usersElement.SelectNodes("user");
            foreach (XmlElement userElement in userNodes)
            {
                Account user = FacebookParser.DeserializeAccount(userElement);
                //if (!user.Session.IsExpired())
                //{
                    users.Add(user);
                //}
            }

            return users;
        }

        /// <summary>
        /// Sets a list of user accounts.
        /// </summary>
        /// <param name="users">
        /// The list of user accounts to set.
        /// </param>
        private void SetUsers(List<Account> users)
        {
            if (users == null)
            {
                throw new ArgumentNullException("users");
            }

            XmlElement usersElement = GetXmlElement("Users", this.preferencesNode);
            usersElement.RemoveAll();

            foreach (Account user in users)
            {
                usersElement.AppendChild(FacebookParser.SerializeAccount(user, this.persistXml));
            }
        }

        /// <summary>
        /// Checks to see if the account_links element is present.
        /// </summary>
        /// <returns>
        /// true if the element is present and false otherwise.
        /// </returns>
        private bool AccountLinksExists()
        {
            XmlNode accountLinksNode = this.preferencesNode.SelectSingleNode("AccountLinks");
            return accountLinksNode != null;
        }

        /// <summary>
        /// Gets a list of all account links.
        /// </summary>
        /// <returns>
        /// A list of account links.
        /// </returns>
        private List<AccountLink> GetAccountLinks()
        {
            XmlElement accountLinksElement = GetXmlElement("AccountLinks", this.preferencesNode);

            List<AccountLink> accountLinks = new List<AccountLink>();

            XmlNodeList accountLinksNodes = accountLinksElement.SelectNodes("account_link");
            foreach (XmlElement accountLinkElement in accountLinksNodes)
            {
                AccountLink accountLink = FacebookParser.DeserializeAccountLink(accountLinkElement);
                accountLinks.Add(accountLink);
            }

            return accountLinks;
        }

        /// <summary>
        /// Sets a list of account links.
        /// </summary>
        /// <param name="users">
        /// The list of account links to set.
        /// </param>
        private void SetAccountLinks(List<AccountLink> accountLinks)
        {
            if (accountLinks == null)
            {
                throw new ArgumentNullException("account_links");
            }

            XmlElement accountLinksElement = GetXmlElement("AccountLinks", this.preferencesNode);
            accountLinksElement.RemoveAll();

            foreach (AccountLink accountLink in accountLinks)
            {
                accountLinksElement.AppendChild(FacebookParser.SerializeAccountLink(accountLink, this.persistXml));
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Checks to see if the persistent data is initialized to the expected format.
        /// </summary>
        public bool IsInitialized()
        {
            XmlNode preferencesNode = this.persistXml.SelectSingleNode("Preferences");
            if (preferencesNode == null)
            {
                return false;
            }

            XmlNode usersNode = preferencesNode.SelectSingleNode("Users");
            XmlNode accountLinksNode = preferencesNode.SelectSingleNode("AccountLinks");
            return usersNode != null && accountLinksNode != null;
        }

        /// <summary>
        /// Initializes the persistXml document to the expected format.
        /// </summary>
        public void Initialize()
        {
            // We may just be upgrading, so before we wipe the XML clean see if we can
            // grab a list of saved users.
            List<XmlElement> savedUsers = GetSavedUsers();

            this.persistXml.RemoveAll();

            XmlProcessingInstruction instruction = 
                this.persistXml.CreateProcessingInstruction("xml", "version=\"1.0\"");
            this.persistXml.AppendChild(instruction);

            XmlElement preferencesElement = this.persistXml.CreateElement("Preferences");
            this.persistXml.AppendChild(preferencesElement);

            XmlElement usersElement = this.persistXml.CreateElement("Users");
            preferencesElement.AppendChild(usersElement);
            foreach(XmlElement savedUserElement in savedUsers)
            {
                usersElement.AppendChild(savedUserElement);
            }

            XmlElement accountLinksElement = this.persistXml.CreateElement("AccountLinks");
            preferencesElement.AppendChild(accountLinksElement);
        }

        /// <summary>
        /// Sets all of the public properties back into the XmlDocument.
        /// </summary>
        public void Save()
        {
            SetUsers(this.users);
            SetAccountLinks(this.accountLinks);
        }

        #endregion
    }
}
